#pragma once

using namespace System::Collections::Generic;
using namespace System::Linq;

#include "FiringEvent.h"

namespace SpikingNeuronLib
{
	public ref class Triplet : IComparable
	{
	private:
		List<FiringEvent>^ _triplet;

		int GetTime(FiringEvent ev) { return ev.Time; }
		int GetNeuron(FiringEvent ev) { return ev.NeuronIndex; }

	public:
		Triplet(array<FiringEvent>^ events);
		Triplet(FiringEvent event1, FiringEvent event2, FiringEvent event3);

		property List<FiringEvent>^ Events
		{
		public:
			List<FiringEvent>^ get()
			{
				return _triplet;
			}
		}

		// The firing times of the three events in the triplet
		property System::Collections::Generic::IEnumerable<int>^ FiringTimes
		{
		public:
			System::Collections::Generic::IEnumerable<int>^ get()
			{
				Func<FiringEvent, int>^ timeSelector = gcnew Func<FiringEvent, int>(this, &Triplet::GetTime);
				return Enumerable::Select<FiringEvent, int>(_triplet, timeSelector);
			}
		}

		// The neurons that fire in the three triplet events
		property System::Collections::Generic::IEnumerable<int>^ Neurons
		{
		public:
			System::Collections::Generic::IEnumerable<int>^ get()
			{
				Func<FiringEvent, int>^ neuronSelector = gcnew Func<FiringEvent, int>(this, &Triplet::GetNeuron);
				return Enumerable::Select<FiringEvent, int>(_triplet, neuronSelector);
			}
		}

		// The time span between the first firing and last firing event (in milliseconds)
		property int TimeSpan
		{
		public:
			int get()
			{
				int min = Enumerable::Min(this->FiringTimes);
				int max = Enumerable::Max(this->FiringTimes);
				return max - min;
			}
		}

		// Return the triplet as a three-tuple
		property Tuple<Tuple<int, int>^, Tuple<int, int>^, Tuple<int, int>^>^ AsThreeTuple
		{
		public:
			Tuple<Tuple<int, int>^, Tuple<int, int>^, Tuple<int, int>^>^ get()
			{
				return gcnew Tuple<Tuple<int, int>^, Tuple<int, int>^, Tuple<int, int>^>(
					gcnew Tuple<int, int>(_triplet[0].Time, _triplet[0].NeuronIndex),
					gcnew Tuple<int, int>(_triplet[1].Time, _triplet[1].NeuronIndex),
					gcnew Tuple<int, int>(_triplet[2].Time, _triplet[2].NeuronIndex)
					);
			}
		}

		// Return the triplet as a six-tuple
		property Tuple<int, int, int, int, int, int>^ AsSixTuple
		{
		public:
			Tuple<int, int, int, int, int, int>^ get()
			{
				return gcnew Tuple<int, int, int, int, int, int>(
					_triplet[0].Time, _triplet[0].NeuronIndex,
					_triplet[1].Time, _triplet[1].NeuronIndex,
					_triplet[2].Time, _triplet[2].NeuronIndex
					);
			}
		}

		// Two triplets are equivalent if their firing time differences are the same
		// as it is the timing differences between events that must match the connection delays
		bool IsEquivalent(Triplet^ otherTriplet);

		virtual int CompareTo(Object^ otherTriplet);

		// Transpose the firing times of each firing event so that least one event fires at t=0
		// i.e. translate each triplet to a common base for comparison
		Triplet^ Transpose();

		// Return the firing times of the triplet as a combined set of firing times
		// that overlap each spike time with the specified jitter
		System::Collections::Generic::HashSet<int>^ GetFuzzyFiringTimes(int jitter);

		virtual String^ ToString() override;
	};
}